Agencia de Exploradores
Esta actividad NO es un simulacro de examen. Su extensión supera lo que se solicitará en el examen de la convocatoria ordinaria. Además, en este modelo se facilita información para la autocorrección de la prueba (rubrica detallada).
Se pretende que sirva de preparación para la prueba real ya que el formato de enunciado será similar exceptuando los detalles de la rúbrica y los elementos válidos e inválidos esperados en los sets de datos.
Contexto
La agencia Rutas Salvajes organiza expediciones guiadas por distintos entornos naturales. Para mejorar su gestión interna, necesita una aplicación en Python que permita administrar exploradores, expediciones y reservas.
Se deberá implementar el sistema siguiendo los principios de la Programación Orientada a Objetos.
No se permite acceder directamente a los atributos de las clases desde fuera de ellas. Se deberán utilizar propiedades, getters, setters o métodos específicos cuando sea necesario.
Archivos entregados
Se proporciona la siguiente estructura de proyecto. 🔗 Descargar Recursos
examen_final_python/
│
├── main.py
├── explorador.py
├── expediciones.py
├── agencia.py
├── carga_datos.py
│
├── data/
│ ├── exploradores.csv
│ ├── expediciones.csv
│ └── reservas.json
El archivo main.py ya estará implementado y no se deberá modificar.
El alumnado deberá completar los archivos:
explorador.py
expediciones.py
agencia.py
carga_datos.py
Parte 1: Gestión de exploradores
Modifica el fichero explorador.py para crear la clase Explorador.
Clase Explorador
Atributos de instancia
id_explorador
nombre
nivel
presupuesto
id_explorador: cadena de texto.nombre: cadena de texto.nivel: cadena de texto. Puede ser"principiante","intermedio"o"experto".presupuesto: número real.
Restricciones
- El presupuesto no puede ser negativo.
- El nivel solo podrá tener uno de estos valores:
principiante
intermedio
experto
- Si se intenta asignar un presupuesto negativo, se lanzará un
ValueError. - Si se intenta asignar un nivel no válido, se lanzará un
ValueError.
Método mágico
Implementa el método mágico necesario para que, al imprimir un explorador, se muestre con el siguiente formato:
<nombre> (<nivel>) - Presupuesto: <presupuesto> €
Ejemplo:
Laura (intermedio) - Presupuesto: 450.0 €
Parte 2: Expediciones
Modifica el fichero expediciones.py.
Se deberá crear una clase base Expedicion y dos clases hijas:
ExpedicionMontana
ExpedicionSelva
Clase Expedicion
Atributo compartido
Todas las expediciones tendrán un atributo compartido llamado:
empresa
Su valor por defecto será:
Rutas Salvajes
Atributos de instancia
id_expedicion
nombre
dificultad
duracion_dias
precio_base
plazas_totales
plazas_ocupadas
id_expedicion: cadena de texto.nombre: cadena de texto.dificultad:"baja","media"o"alta".duracion_dias: número entero.precio_base: número real.plazas_totales: número entero.plazas_ocupadas: número entero. Por defecto será 0.
Restricciones
precio_baseno puede ser negativo.duracion_diasdebe ser mayor que 0.plazas_totalesdebe ser mayor que 0.plazas_ocupadasno puede ser negativa.plazas_ocupadasno puede ser mayor queplazas_totales.dificultadsolo podrá ser"baja","media"o"alta".
Si alguna de estas restricciones no se cumple, se lanzará un ValueError.
Métodos de la clase Expedicion
calcular_precio_final()
Debe devolver el precio final de la expedición.
El cálculo será:
precio_final = precio_base * multiplicador_dificultad
Los multiplicadores serán:
baja → 1
media → 1.25
alta → 1.5
hay_plazas_disponibles()
Debe devolver True si quedan plazas disponibles y False en caso contrario.
reservar_plaza()
Debe aumentar en 1 el número de plazas ocupadas.
Si no hay plazas disponibles, deberá lanzar un ValueError.
cancelar_plaza()
Debe reducir en 1 el número de plazas ocupadas.
Si no hay plazas ocupadas, deberá lanzar un ValueError.
realizar_expedicion()
Método polimórfico.
En la clase base debe lanzar una excepción NotImplementedError.
Clases hijas
Clase ExpedicionMontana
Hereda de Expedicion.
Atributo específico
altura_maxima
Será un número entero que representa la altura máxima de la ruta en metros.
Sobrescritura de
realizar_expedicion()
Debe devolver el siguiente mensaje:
Ascendiendo hasta <altura_maxima> metros en la expedición <nombre>.
Modificación del precio
Las expediciones de montaña tendrán un suplemento fijo de 40 € sobre el precio final.
Por tanto, en esta clase deberá sobrescribirse el método calcular_precio_final().
Clase ExpedicionSelva
Hereda de Expedicion.
Atributo específico
riesgo_fauna
Será una cadena de texto que podrá tener los valores:
bajo
medio
alto
Si el valor no es válido, se lanzará un ValueError.
Sobrescritura de
realizar_expedicion()
Debe devolver el siguiente mensaje:
Explorando la selva con riesgo de fauna <riesgo_fauna> en la expedición <nombre>.
Modificación del precio
Las expediciones de selva con riesgo de fauna "alto" tendrán un suplemento de 60 € sobre el precio final.
Las expediciones de selva con riesgo "bajo" o "medio" no tendrán suplemento.
Parte 3: Objeto Agencia
Crea la clase Agencia en el fichero agencia.py.
La agencia deberá gestionar exploradores, expediciones y reservas.
Atributos
exploradores
expediciones
reservas
exploradores: diccionario donde la clave será el ID del explorador y el valor será un objetoExplorador.expediciones: diccionario donde la clave será el ID de la expedición y el valor será un objetoExpedicionMontanaoExpedicionSelva.reservas: lista de diccionarios con la información de las reservas realizadas.
No se podrán modificar directamente estos atributos desde fuera de la clase.
Parte 4: Métodos de la agencia
La clase Agencia deberá contener los siguientes métodos.
agregar_explorador(explorador)
Recibe un objeto Explorador.
Debe añadirlo al diccionario de exploradores.
Si ya existe un explorador con el mismo ID, se lanzará un ValueError.
agregar_expedicion(expedicion)
Recibe un objeto de tipo Expedicion.
Debe añadirlo al diccionario de expediciones.
Si ya existe una expedición con el mismo ID, se lanzará un ValueError.
buscar_explorador(id_explorador)
Recibe el ID de un explorador.
Debe devolver el objeto Explorador correspondiente.
Si no existe, se lanzará un ValueError.
buscar_expedicion(id_expedicion)
Recibe el ID de una expedición.
Debe devolver el objeto Expedicion correspondiente.
Si no existe, se lanzará un ValueError.
realizar_reserva(id_explorador, id_expedicion)
Recibe el ID de un explorador y el ID de una expedición.
Debe realizar una reserva si se cumplen todas estas condiciones:
- El explorador existe.
- La expedición existe.
- La expedición tiene plazas disponibles.
- El explorador tiene presupuesto suficiente para pagar la expedición.
Si se realiza la reserva:
- Se ocupará una plaza en la expedición.
- Se restará el precio final de la expedición al presupuesto del explorador.
- Se añadirá una reserva a la lista de reservas.
La reserva se guardará como un diccionario con esta estructura:
{
"id_explorador": "EX001",
"id_expedicion": "EP001",
"precio_pagado": 275.0
}
Si no se puede realizar la reserva, se lanzará un ValueError.
cancelar_reserva(id_explorador, id_expedicion)
Debe cancelar una reserva existente.
Si existe la reserva:
- Se libera una plaza de la expedición.
- Se devuelve el importe pagado al presupuesto del explorador.
- Se elimina la reserva de la lista.
Si la reserva no existe, se lanzará un ValueError.
expedicion_mas_cara()
Debe devolver el objeto expedición con mayor precio final.
expediciones_disponibles()
Debe devolver una lista con las expediciones que tienen plazas disponibles.
recaudacion_total()
Debe devolver la suma de todos los precios pagados en las reservas realizadas.
tipo_mas_rentable()
Debe devolver qué tipo de expedición ha generado mayor recaudación.
Devolverá uno de estos textos:
montana
selva
empate
sin reservas
Parte 5: Carga de datos
Modifica el fichero carga_datos.py.
Se deberán implementar las siguientes funciones:
cargar_exploradores(agencia, path_fichero)
Recibe:
agencia
path_fichero
Debe leer el archivo exploradores.csv, crear objetos Explorador y añadirlos a la agencia.
Debe devolver el objeto agencia modificado.
Las filas incorrectas se ignorarán.
cargar_expediciones(agencia, path_fichero)
Recibe:
agencia
path_fichero
Debe leer el archivo expediciones.csv, crear objetos ExpedicionMontana o ExpedicionSelva según corresponda y añadirlos a la agencia.
Debe devolver el objeto agencia modificado.
Las filas incorrectas se ignorarán.
cargar_reservas(agencia, path_fichero)
Recibe:
agencia
path_fichero
Debe leer el archivo reservas.json y realizar las reservas iniciales usando el método realizar_reserva().
Debe devolver el objeto agencia modificado.
Las reservas incorrectas se ignorarán.
Parte 6: Guardado de datos
En el fichero carga_datos.py, implementa también la función:
guardar_reservas(agencia, path_fichero)
Recibe:
agencia
path_fichero
Debe guardar en un archivo JSON todas las reservas actuales de la agencia.
La estructura del archivo generado será:
[
{
"id_explorador": "EX001",
"id_expedicion": "EP001",
"precio_pagado": 275.0
}
]
Parte 7: Métodos mágicos
Implementa el método mágico necesario para que, al imprimir una expedición, se muestre con el siguiente formato:
<nombre> | <dificultad> | <duracion_dias> días | <precio_final> €
Ejemplo:
Ruta del Dragón | alta | 4 días | 490.0 €
Datasets
Dataset 1: exploradores.csv
id_explorador,nombre,nivel,presupuesto
EX001,Laura,intermedio,500
EX002,Marcos,principiante,180
EX003,Sara,experto,900
EX004,Diego,intermedio,300
EX005,Nerea,experto,700
EX006,Pablo,principiante,-50
EX007,Irene,avanzado,450
EX008,,intermedio,250
EX009,Hugo,principiante,220
EX010,Ana,experto,notiene
Filas válidas esperadas
EX001
EX002
EX003
EX004
EX005
EX009
Filas que deben ignorarse
EX006 → presupuesto negativo
EX007 → nivel no válido
EX008 → nombre vacío
EX010 → presupuesto no numérico
Dataset 2: expediciones.csv
id_expedicion,tipo,nombre,dificultad,duracion_dias,precio_base,plazas_totales,extra
EP001,montana,Ruta del Dragón,alta,4,300,3,2400
EP002,selva,Sendero Esmeralda,media,3,180,2,medio
EP003,montana,Cumbre Blanca,media,2,150,2,1800
EP004,selva,Río Oculto,alta,5,260,1,alto
EP005,montana,Ladera Norte,baja,1,90,4,900
EP006,desierto,Dunas Rojas,media,3,200,2,calor
EP007,selva,Niebla Verde,baja,2,-120,3,bajo
EP008,montana,Pico Sombrío,alta,0,280,2,2100
EP009,selva,Pantano Azul,media,3,160,0,bajo
EP010,selva,Raíces Antiguas,alta,4,240,2,extremo
Interpretación del campo extra
- Si el tipo es
montana, el campoextrarepresentaaltura_maxima. - Si el tipo es
selva, el campoextrarepresentariesgo_fauna.
Filas válidas esperadas
EP001
EP002
EP003
EP004
EP005
Filas que deben ignorarse
EP006 → tipo no válido
EP007 → precio negativo
EP008 → duración igual a 0
EP009 → plazas totales igual a 0
EP010 → riesgo de fauna no válido
Dataset 3: reservas.json
[
{
"id_explorador": "EX001",
"id_expedicion": "EP001"
},
{
"id_explorador": "EX002",
"id_expedicion": "EP002"
},
{
"id_explorador": "EX003",
"id_expedicion": "EP004"
},
{
"id_explorador": "EX004",
"id_expedicion": "EP004"
},
{
"id_explorador": "EX005",
"id_expedicion": "EP003"
},
{
"id_explorador": "EX009",
"id_expedicion": "EP005"
},
{
"id_explorador": "EX999",
"id_expedicion": "EP001"
},
{
"id_explorador": "EX001",
"id_expedicion": "EP999"
}
]
Reservas válidas esperadas
EX001 → EP001
EX002 → EP002
EX003 → EP004
EX005 → EP003
EX009 → EP005
Reservas que deben ignorarse
EX004 → EP004
Porque EP004 solo tiene 1 plaza y ya ha sido reservada por EX003.
EX999 → EP001
Porque el explorador no existe.
EX001 → EP999
Porque la expedición no existe.
Comprobaciones esperadas
Estas comprobaciones pueden servir para el main.py o para la corrección.
Precios finales esperados
EP001 → montaña, alta → 300 * 1.5 + 40 = 490.0
EP002 → selva, media, riesgo medio → 180 * 1.25 = 225.0
EP003 → montaña, media → 150 * 1.25 + 40 = 227.5
EP004 → selva, alta, riesgo alto → 260 * 1.5 + 60 = 450.0
EP005 → montaña, baja → 90 * 1 + 40 = 130.0
Presupuestos después de cargar reservas válidas
EX001 → 500 - 490 = 10.0
EX002 → 180 - 225 → no se debería realizar, presupuesto insuficiente
EX003 → 900 - 450 = 450.0
EX005 → 700 - 227.5 = 472.5
EX009 → 220 - 130 = 90.0
Por tanto, realmente las reservas válidas finales deberían ser:
EX001 → EP001
EX003 → EP004
EX005 → EP003
EX009 → EP005
La reserva de EX002 → EP002 debe ignorarse porque el explorador no tiene presupuesto suficiente.
Resultado esperado de reservas cargadas
Tras cargar los datos iniciales, deberían quedar estas reservas:
[
{
"id_explorador": "EX001",
"id_expedicion": "EP001",
"precio_pagado": 490.0
},
{
"id_explorador": "EX003",
"id_expedicion": "EP004",
"precio_pagado": 450.0
},
{
"id_explorador": "EX005",
"id_expedicion": "EP003",
"precio_pagado": 227.5
},
{
"id_explorador": "EX009",
"id_expedicion": "EP005",
"precio_pagado": 130.0
}
]
Rúbrica de evaluación
Calificación total: 10 puntos
| Parte | Criterio | Puntuación |
|---|---|---|
| Parte 1 | Clase Explorador | 1 |
| Parte 2 | Clase base Expedicion | 1,5 |
| Parte 2 | Herencia y polimorfismo | 1,5 |
| Parte 3 y 4 | Clase Agencia y lógica de negocio | 2 |
| Parte 5 | Carga de datos CSV y JSON | 1,5 |
| Parte 6 | Guardado de reservas en JSON | 0,75 |
| Parte 7 | Métodos mágicos y representación textual | 0,5 |
| General | Calidad, organización y encapsulación | 1,25 |
Desglose detallado
1. Clase Explorador — 1 punto
| Aspecto | Puntos |
|---|---|
| Define correctamente los atributos indicados | 0,25 |
| Valida correctamente el presupuesto | 0,25 |
| Valida correctamente el nivel | 0,25 |
Implementa correctamente el método mágico __str__ | 0,25 |
2. Clase base Expedicion — 1,5 puntos
| Aspecto | Puntos |
|---|---|
Define correctamente el atributo compartido empresa | 0,2 |
| Define correctamente los atributos de instancia | 0,3 |
| Valida precio, duración, plazas y dificultad | 0,4 |
| Calcula correctamente el precio final base | 0,25 |
Implementa correctamente hay_plazas_disponibles() | 0,15 |
Implementa correctamente reservar_plaza() y cancelar_plaza() | 0,2 |
3. Herencia y polimorfismo — 1,5 puntos
| Aspecto | Puntos |
|---|---|
ExpedicionMontana hereda correctamente de Expedicion | 0,25 |
ExpedicionSelva hereda correctamente de Expedicion | 0,25 |
| Se inicializan correctamente los atributos de la clase padre | 0,25 |
| Se implementan los atributos específicos | 0,2 |
Se sobrescribe correctamente realizar_expedicion() | 0,25 |
Se sobrescribe correctamente calcular_precio_final() cuando corresponde | 0,3 |
4. Clase Agencia y lógica de negocio — 2 puntos
| Aspecto | Puntos |
|---|---|
| Gestiona correctamente exploradores, expediciones y reservas | 0,3 |
Implementa agregar_explorador() y agregar_expedicion() | 0,25 |
| Implementa correctamente los métodos de búsqueda | 0,25 |
| Realiza reservas comprobando existencia, plazas y presupuesto | 0,5 |
| Cancela reservas devolviendo presupuesto y plaza | 0,3 |
| Calcula correctamente la expedición más cara | 0,15 |
| Devuelve correctamente expediciones disponibles | 0,1 |
| Calcula correctamente la recaudación total | 0,1 |
| Calcula correctamente el tipo más rentable | 0,1 |
5. Carga de datos CSV y JSON — 1,5 puntos
| Aspecto | Puntos |
|---|---|
Carga correctamente exploradores.csv | 0,35 |
| Ignora exploradores inválidos sin detener el programa | 0,25 |
Carga correctamente expediciones.csv | 0,35 |
| Ignora expediciones inválidas sin detener el programa | 0,25 |
Carga correctamente reservas.json | 0,2 |
| Ignora reservas inválidas sin detener el programa | 0,1 |
6. Guardado de reservas en JSON — 0,75 puntos
| Aspecto | Puntos |
|---|---|
| Crea correctamente el archivo JSON de salida | 0,25 |
| Guarda todas las reservas actuales | 0,25 |
| Respeta la estructura indicada | 0,25 |
7. Métodos mágicos — 0,5 puntos
| Aspecto | Puntos |
|---|---|
__str__ de Explorador correcto | 0,2 |
__str__ de Expedicion correcto | 0,3 |
8. Calidad, organización y encapsulación — 1,25 puntos
| Aspecto | Puntos |
|---|---|
| No accede directamente a atributos desde fuera de las clases | 0,3 |
| Código organizado por ficheros y responsabilidades | 0,25 |
| Nombres claros y coherentes | 0,2 |
| Uso adecuado de excepciones | 0,2 |
| Código legible y sin duplicación excesiva | 0,2 |
| Respeta nombres de clases, métodos y retornos solicitados | 0,1 |
Penalizaciones generales
| Situación | Penalización |
|---|---|
| El programa no ejecuta por errores de sintaxis | Hasta -3 |
| No se usa POO | Hasta -4 |
| No se implementa herencia | Hasta -1,5 |
| No se implementa polimorfismo | Hasta -1 |
| No se leen archivos externos | Hasta -2 |
| No se gestiona JSON | Hasta -1 |
| No se respeta la estructura del proyecto | Hasta -1 |
| Se accede directamente a los atributos desde fuera de las clases de forma reiterada | Hasta -1 |
| El programa se detiene ante datos erróneos del dataset | Hasta -1 |
| No se respetan los nombres pedidos de clases, métodos o retornos | Hasta -1,5 |
Observaciones
- No se deberá modificar el archivo
main.py. - Se deberán respetar exactamente los nombres de clases, métodos y valores de retorno indicados.
- Las filas incorrectas de los archivos de datos deberán ignorarse sin detener el programa.
- Se permite el uso de excepciones para controlar errores.
- Se valorará la claridad, la organización y la correcta separación de responsabilidades.
- El acceso directo a atributos desde fuera de las clases no está permitido.
- La solución debe poder ejecutarse cargando los datos desde los archivos proporcionados.